package pay.utils;

import com.alibaba.fastjson.JSON;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component
public class RedisUtils {

    public enum RedisDB {
        APP_TOKEN(0), // 0 号库
        APP_BIZ(1),  // 1 号库
        TOKEN(2), // 2 号库
        BIZ(3);   // 3 号库
        private final Integer dbIndex;

        RedisDB(Integer dbIndex) {
            this.dbIndex = dbIndex;
        }

        public Integer getDbIndex() {
            return dbIndex;
        }
    }

    @Resource(name = "appTokenRedisTemplate")
    private RedisTemplate<String, Object> appTokenRedis;

    @Resource(name = "appBizRedisTemplate")
    private RedisTemplate<String, Object> appBizRedis;

    @Resource(name = "tokenRedisTemplate")
    private RedisTemplate<String, Object> tokenRedis;

    @Resource(name = "bizRedisTemplate")
    private RedisTemplate<String, Object> bizRedis;

    private RedisTemplate<String, Object> getTemplate(RedisDB db) {
        switch (db) {
            case APP_TOKEN:
                return appTokenRedis;
            case APP_BIZ:
                return appBizRedis;
            case TOKEN:
                return tokenRedis;
            case BIZ:
                return bizRedis;
            default:
                throw new IllegalArgumentException("不支持的 Redis 库类型");
        }
    }

    public void set(String key, Object value, long timeout, TimeUnit unit, RedisDB db) {
        getTemplate(db).opsForValue().set(key, value, timeout, unit);
    }

    public Object get(String key, RedisDB db) {
        return getTemplate(db).opsForValue().get(key);
    }

    public boolean del(String key, RedisDB db) {
        return getTemplate(db).delete(key);
    }

    public boolean hasKey(String key, RedisDB db) {
        return getTemplate(db).hasKey(key);
    }

    public boolean expire(String key, long timeout, TimeUnit unit, RedisDB db) {
        return getTemplate(db).expire(key, timeout, unit);
    }

    public boolean setIfAbsent(String key, String value, long timeout, RedisDB db) {
        return Boolean.TRUE.equals(getTemplate(db).opsForValue().setIfAbsent(key, value, Duration.ofSeconds(timeout)));
    }

    public long incr(String key, RedisDB db) {
        return getTemplate(db).opsForValue().increment(key);
    }

    public long incrBy(String key, long increment, RedisDB db) {
        return getTemplate(db).opsForValue().increment(key, increment);
    }

    public void setHash(String key, Map<String, Object> map, RedisDB db) {
        getTemplate(db).opsForHash().putAll(key, map);
    }

    public Map<Object, Object> getHash(String key, RedisDB db) {
        return getTemplate(db).opsForHash().entries(key);
    }

    public void deleteHashField(String key, String field, RedisDB db) {
        getTemplate(db).opsForHash().delete(key, field);
    }

    public void addToSet(String key, Object value, RedisDB db) {
        if (value instanceof String) {
            getTemplate(db).opsForSet().add(key, value);
        } else {
            String string = JSON.toJSONString(value);
            getTemplate(db).opsForSet().add(key, string);
        }
    }

    public Set<Object> getSet(String key, RedisDB db) {
        return getTemplate(db).opsForSet().members(key);
    }


    /**
     * 从 Redis 获取指定类型的 Set<T>
     *
     * @param key   redis key
     * @param clazz 目标类型
     * @param db    RedisDB 枚举
     * @return Set<T>
     */
    public <T> Set<T> getSet(String key, Class<T> clazz, RedisDB db) {
        Set<Object> rawSet = getTemplate(db).opsForSet().members(key);
        if (rawSet == null || rawSet.isEmpty()) {
            return null;
        }
        return rawSet.stream()
                .map(s -> {
                    try {
                        if (clazz == String.class) {
                            // 直接返回字符串，不做反序列化
                            return clazz.cast(s);
                        } else {
                            return JSON.parseObject((String) s, clazz);
                        }
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                })
                .collect(Collectors.toSet());
    }

    public <T> List<T> getList(String key, Class<T> clazz, RedisDB db) {
        Object obj = get(key, db);
        if (obj == null) return Collections.emptyList();

        if (obj instanceof List<?>) {
            List<?> rawList = (List<?>) obj;
            List<T> result = new ArrayList<>();
            for (Object o : rawList) {
                if (clazz.isInstance(o)) {
                    result.add(clazz.cast(o));
                }
            }
            return result;
        }
        return Collections.emptyList();
    }

    public <T> T getObject(String key, Class<T> clazz, RedisDB db) {
        Object data = get(key, db);
        if (clazz.isInstance(data)) {
            return clazz.cast(data);
        }
        return null;
    }

    public void removeFromSet(String key, Object value, RedisDB db) {
        getTemplate(db).opsForSet().remove(key, value);
    }

    @Resource(name = "bizStringRedisTemplate")
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 原子扣库存（Lua 安全）
     *
     * @return 1=成功, -1=库存不足, -2=库存未初始化, -3=参数错误
     */
    public Long decrStockSafe(String key, long delta) {
        if (delta <= 0) throw new IllegalArgumentException("delta 必须大于0");

        String lua = "local stockStr = redis.call('GET', KEYS[1]) " +
                "if not stockStr then return -2 end " +
                "local stock = tonumber(stockStr) " +
                "local delta = tonumber(ARGV[1]) " +
                "if not stock or not delta then return -3 end " +
                "if stock < delta then return -1 end " +
                "redis.call('DECRBY', KEYS[1], delta) " +
                "return 1";

        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(lua);
        script.setResultType(Long.class);

        return stringRedisTemplate.execute(script, Collections.singletonList(key), String.valueOf(delta));
    }

    public void incrStock(String key, long delta) {
        stringRedisTemplate.opsForValue().increment(key, delta);
    }

    public String getStock(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 入队（队尾入队），支持任意对象类型，会自动序列化为 JSON 字符串。
     *
     * @param key Redis 队列 key
     * @param value 入队对象
     * @param db RedisDB 枚举，指定使用哪一个 Redis 库
     */
    public void enqueue(String key, Object value, RedisDB db) {
        String string = value instanceof String ? (String) value : JSON.toJSONString(value);
        getTemplate(db).opsForList().rightPush(key, string);
    }

    /**
     * 出队（队头出队，非阻塞），支持反序列化为指定类型。
     *
     * @param key Redis 队列 key
     * @param clazz 队列元素类型
     * @param db RedisDB 枚举
     * @param <T> 出队对象类型
     * @return 队列头元素，队列为空返回 null
     */
    public <T> T dequeue(String key, Class<T> clazz, RedisDB db) {
        Object obj = getTemplate(db).opsForList().leftPop(key);
        if (obj == null) return null;

        if (clazz == String.class) {
            return clazz.cast(obj);
        } else {
            return JSON.parseObject((String) obj, clazz);
        }
    }

    /**
     * 阻塞出队（队头出队，等待超时），支持反序列化为指定类型。
     *
     * @param key Redis 队列 key
     * @param timeoutSeconds 超时时间（秒），超时返回 null
     * @param clazz 队列元素类型
     * @param db RedisDB 枚举
     * @param <T> 出队对象类型
     * @return 队列头元素，超时或队列为空返回 null
     */
    public <T> T dequeueBlocking(String key, long timeoutSeconds, Class<T> clazz, RedisDB db) {
        Object obj = getTemplate(db).opsForList().leftPop(key, Duration.ofSeconds(timeoutSeconds));
        if (obj == null) return null;

        if (clazz == String.class) {
            return clazz.cast(obj);
        } else {
            return JSON.parseObject((String) obj, clazz);
        }
    }

    /**
     * 获取队列长度
     *
     * @param key Redis 队列 key
     * @param db RedisDB 枚举
     * @return 队列长度
     */
    public long queueSize(String key, RedisDB db) {
        Long size = getTemplate(db).opsForList().size(key);
        return size != null ? size : 0;
    }

    /**
     * 从 Redis 队列中删除指定值（只删除一个匹配项）
     *
     * @param key Redis 队列 key
     * @param value 要删除的值（Object，会自动序列化为 JSON 字符串）
     * @param db RedisDB 枚举，指定 Redis 库
     * @return 是否删除成功（true = 删除了至少一个元素）
     */
    public boolean removeOneFromQueue(String key, Object value, RedisDB db) {
        String string = value instanceof String ? (String) value : JSON.toJSONString(value);
        // count=1 表示只删除一个匹配项
        Long removed = getTemplate(db).opsForList().remove(key, 1, string);
        return removed != null && removed > 0;
    }

}
